package edu.thu.thss.yxy.sip;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TooManyListenersException;

import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.*;

import edu.thu.thss.yxy.sip.event.CallEvent;
import edu.thu.thss.yxy.sip.event.CallListener;
import edu.thu.thss.yxy.sip.event.RegistrationStateChangeEvent;
import edu.thu.thss.yxy.sip.security.SipSecurityManager;
import edu.thu.thss.yxy.util.Logger;
import edu.thu.thss.yxy.util.NetworkUtil;
import edu.thu.thss.yxy.util.PropertiesUtil;
import gov.nist.javax.sip.address.AddressFactoryImpl;
import gov.nist.javax.sip.header.HeaderFactoryImpl;
import gov.nist.javax.sip.message.MessageFactoryImpl;

public class SipManager implements SipListener {
	/**
	 * Our class logger.
	 */
	private static final Logger logger = Logger.getLogger(SipManager.class);

	/**
     * Specifies the number of retries that should be attempted when deleting
     * a sipProvider
     */
    protected static final int  RETRY_OBJECT_DELETES       = 10;
    /**
     * Specifies the time to wait before retrying delete of a sipProvider.
     */
    protected static final long RETRY_OBJECT_DELETES_AFTER = 500;
    
	private static final String DEFAULT_TRANSPORT = "udp";
	
	/**
     * Default number of times that our requests can be forwarded.
     */
    private static final int  MAX_FORWARDS = 70;

	/**
	 * The SipFactory instance used to create the SipStack and the Address
	 * Message and Header Factories.
	 */
	public SipFactory sipFactory;

	/**
	 * The AddressFactory used to create URLs ans Address objects.
	 */
	public AddressFactory addressFactory;

	/**
	 * The HeaderFactory used to create SIP message headers.
	 */
	public HeaderFactory headerFactory;

	/**
	 * The Message Factory used to create SIP messages.
	 */
	public MessageFactory messageFactory;

	/**
	 * The sipStack instance that handles SIP communications.
	 */
	SipStack sipStack;

	/**
	 * The default (and currently the only) SIP listening point of the
	 * application.
	 */
	ListeningPoint listeningPoint;

	/**
	 * The JAIN SIP SipProvider instance.
	 */
	public SipProvider sipProvider;

	/**
	 * The SipSecurityManager instance that would be taking care of our
	 * authentications.
	 */
	private SipSecurityManager sipSecurityManager = null;

	/**
	 * The default maxForwards header that we use in our requests.
	 */
	private MaxForwardsHeader maxForwardsHeader = null;

	/**
	 * The header that we use to identify ourselves.
	 */
//	private UserAgentHeader userAgentHeader = null;

	protected String currentlyUsedURI = null;

	
	protected ArrayList<CallListener> callListeners = new ArrayList<CallListener>();
	
	private SipTelephonyProcessor telePhonyProcessor = null;
	private SipRegistrarProcessor registrarProcessor = null;

	// properties
	private String sipStackPath = null;
	private String publicAddress = null;
	private String ourDisplayName = null;
	private String transport = null;
	private String registrarAddress = null;
	private int localPort = -1;
	@SuppressWarnings("unused")
	private int registrarPort = -1;
	@SuppressWarnings("unused")
	private int registrationsExpiration = -1;
	private String registrarTransport = null;
	// mandatory stack properties
	private String stackAddress = null;
	private String stackName = "GoPhone";
	
	
	//Prebuilt Message headers
    private FromHeader fromHeader = null;
    private ContactHeader contactHeader = null;
    private ArrayList<ViaHeader> viaHeaders = null;
    private boolean isStarted = false;
	

	public SipManager() {
		registrarProcessor = new SipRegistrarProcessor(this);
		telePhonyProcessor = new SipTelephonyProcessor(this);
	}

	/**
	 * Returns the AddressFactory used to create URLs ans Address objects.
	 * 
	 * @return the AddressFactory used to create URLs ans Address objects.
	 */
	public AddressFactory getAddressFactory() {
		return addressFactory;
	}

	/**
	 * Returns the HeaderFactory used to create SIP message headers.
	 * 
	 * @return the HeaderFactory used to create SIP message headers.
	 */
	public HeaderFactory getHeaderFactory() {
		return headerFactory;
	}

	/**
	 * Returns the Message Factory used to create SIP messages.
	 * 
	 * @return the Message Factory used to create SIP messages.
	 */
	public MessageFactory getMessageFactory() {
		return messageFactory;
	}
	
	public SipProvider getJainSipProvider() {
		return sipProvider;
	}

	/**
	 * Reurns the currently valid sip security manager that everyone should use
	 * to authenticate SIP Requests.
	 * 
	 * @return the currently valid instace of a SipSecurityManager that everyone
	 *         sould use to authenticate SIP Requests.
	 */
	public SipSecurityManager getSipSecurityManager() {
		return sipSecurityManager;
	}

	public String getTransport() {
		return transport;
	}

	public void start() throws OperationFailedException {
		initProperties();
		this.sipFactory = SipFactory.getInstance();
        sipFactory.setPathName(sipStackPath);
//		try {
//			addressFactory = sipFactory.createAddressFactory();
//			headerFactory = sipFactory.createHeaderFactory();
//			messageFactory = sipFactory.createMessageFactory();
			addressFactory = new AddressFactoryImpl();
			headerFactory = new HeaderFactoryImpl();
			messageFactory = new MessageFactoryImpl();
//		} catch (PeerUnavailableException ex) {
//			throw new OperationFailedException(
//					"Could not could not create factories!",
//					OperationFailedException.GENERAL_ERROR, ex);
//		}
		
		try {
			sipStack = sipFactory.createSipStack(PropertiesUtil.getSipProperties());
		} catch (PeerUnavailableException ex) {
			throw new OperationFailedException(
					"Could not could not create SipStack!\n",
					OperationFailedException.GENERAL_ERROR, ex);
		}
		
		try {
			boolean successfullyBound = false;
			while (!successfullyBound) {
				try {
					listeningPoint = sipStack.createListeningPoint(
							NetworkUtil.IN_ADDR_ANY, localPort, transport);
				} catch (InvalidArgumentException ex) {
					// choose another port between 1024 and 65000
					logger.info("error binding stack to port " + localPort
							+ ". Will try another port", ex);

					localPort = (int) ((65000 - 1024) * Math.random()) + 1024;
					continue;
				}
				successfullyBound = true;
			}
		} catch (TransportNotSupportedException ex) {
			logger.error("Transport "
									+ transport
									+ " is not suppported by the stack!\n Try specifying another"
									+ " transport in Mais property files.\n",
							ex);
			throw new OperationFailedException(
					"Transport "
							+ transport
							+ " is not suppported by the stack!\n Try specifying another"
							+ " transport in Mais property files.\n",
					OperationFailedException.INTERNAL_ERROR, ex);
		}
		try {
			sipProvider = sipStack.createSipProvider(listeningPoint);
		} catch (ObjectInUseException ex) {
			logger.error("Could not create factories!\n", ex);
			throw new OperationFailedException("Could not create factories!\n",
					OperationFailedException.INTERNAL_ERROR, ex);
		}
		try {
			sipProvider.addSipListener(this);
		} catch (TooManyListenersException exc) {
			logger.error("Could not register SipManager as a sip listener!",
					exc);
			throw new OperationFailedException(
					"Could not register SipManager as a sip listener!",
					OperationFailedException.INTERNAL_ERROR, exc);
		}
         
		//Make sure prebuilt headers are nulled so that they get reinited
        //if this is a restart
        contactHeader = null;
        fromHeader = null;
        viaHeaders = null;
        maxForwardsHeader = null;
        isStarted = true;
        
	}
	
	/**
     * Unregisters listening points, deletes sip providers, and generally
     * prepares the stack for a re-start(). This method is meant to be used
     * when properties are changed and should be reread by the stack.
     * @throws CommunicationsException
     */
    public void stop() throws OperationFailedException
    {
       
            //Delete SipProvider
            int tries = 0;
            for (tries = 0; tries < RETRY_OBJECT_DELETES; tries++) {
                try {
                    sipStack.deleteSipProvider(sipProvider);
                }
                catch (ObjectInUseException ex) {
                    // System.err.println("Retrying delete of riSipProvider!");
                    sleep(RETRY_OBJECT_DELETES_AFTER);
                    continue;
                }
                break;
            }
            if (tries >= RETRY_OBJECT_DELETES)
                throw new OperationFailedException(
                    "Failed to delete the sipProvider!", OperationFailedException.GENERAL_ERROR);

            //Delete RI ListeningPoint
            for (tries = 0; tries < RETRY_OBJECT_DELETES; tries++) {
                try {
                    sipStack.deleteListeningPoint(listeningPoint);
                }
                catch (ObjectInUseException ex) {
                    //System.err.println("Retrying delete of riListeningPoint!");
                    sleep(RETRY_OBJECT_DELETES_AFTER);
                    continue;
                }
                break;
            }
            if (tries >= RETRY_OBJECT_DELETES)
                throw new OperationFailedException(
                    "Failed to delete a listeningPoint!", OperationFailedException.GENERAL_ERROR);

            sipProvider = null;
            listeningPoint = null;
            addressFactory = null;
            messageFactory = null;
            headerFactory = null;
            sipStack = null;

            viaHeaders = null;
            contactHeader = null;
            fromHeader = null;
    }
    
    /**
     * Waits during _no_less_ than sleepFor milliseconds.
     * Had to implement it on top of Thread.sleep() to guarantee minimum
     * sleep time.
     *
     * @param sleepFor the number of miliseconds to wait
     */
	protected static void sleep(long sleepFor) {

		long startTime = System.currentTimeMillis();
		long haveBeenSleeping = 0;
		while (haveBeenSleeping < sleepFor) {
			try {
				Thread.sleep(sleepFor - haveBeenSleeping);
			} catch (InterruptedException ex) {
				// we-ll have to wait again!
			}
			haveBeenSleeping = (System.currentTimeMillis() - startTime);
		}
	}

	public boolean isStarted() {
		return isStarted;
	}

	public void register() throws OperationFailedException {
		if (isStarted){
			registrarProcessor.register();
		}else{
			throw new OperationFailedException(
					"Could not could not create SipStack!\n",
					OperationFailedException.GENERAL_ERROR);
		}
	}

	public void endAllCalls() {
		if (telePhonyProcessor == null) {
			return;
		}
		try {
			Iterator<Call> activeCalls = telePhonyProcessor.getActiveCalls();
			while (activeCalls.hasNext()) {
				Call call = activeCalls.next();

				hangupCall(call.getCallID());

			}
		} catch (OperationFailedException ex) {
			logger.error("Failed to end all calls", ex);
		}
	}

	public void unregister() throws OperationFailedException {
		if (getRegistrationState().equals(RegistrationState.UNREGISTERED)
				|| getRegistrationState().equals(
						RegistrationState.UNREGISTERING)
				|| getRegistrationState().equals(
						RegistrationState.CONNECTION_FAILED)) {
			return;
		}

		registrarProcessor.unregister();
	}

	/**
	 * Generates a ToTag and attaches it to the to header of <tt>response</tt>.
	 * 
	 * @param response
	 *            the response that is to get the ToTag.
	 * @param containingDialog
	 *            the <tt>Dialog</tt> instance that the response would be sent
	 *            in or <tt>null</tt> if we are not aware of the <tt>Dialog</tt>
	 *            when calling this method.
	 */
	public void attachToTag(Response response, Dialog containingDialog) {
		ToHeader to = (ToHeader) response.getHeader(ToHeader.NAME);
		if (to == null) {
			logger.debug("Strange ... no to To header in response:" + response);
			return;
		}

		if (containingDialog != null && containingDialog.getLocalTag() != null) {
			logger.debug("We seem to already have a tag in this dialog. "
					+ "Returning");
			return;
		}

		try {
			if (to.getTag() == null || to.getTag().trim().length() == 0) {
				String toTag = Integer.toHexString(new Random().nextInt());

				logger.debug("generated to tag: " + toTag);
				to.setTag(toTag);
			}
		} catch (ParseException ex) {
			// a parse exception here mean an internal error so we can only log
			logger.error("Failed to attach a to tag to an outgoing response.",
					ex);
		}
	}
	
	/**
     * Returns the user used to create the From Header URI.
     * @return the user used to create the From Header URI.
     */
	public String getLocalUser() {
		try {
			return ((SipURI) getFromHeader().getAddress().getURI()).getUser();
		} catch (OperationFailedException ex) {
			return "";
		}
	}

	public void setCurrentlyUsedURI(String currentURI) {
		this.currentlyUsedURI = currentURI;
	}

	private void initProperties() {
		// ------------------ stack properties --------------
		stackAddress = PropertiesUtil.getProperty("javax.sip.IP_ADDRESS");
		if (stackAddress == null || "".equals(stackAddress)) {
			stackAddress = getLocalHostAddress();
			// Add the host address to the properties that will pass the stack
			PropertiesUtil.setProperty("javax.sip.IP_ADDRESS", stackAddress);
		}
		stackAddress.trim();
		// ensure IPv6 address compliance
		if (stackAddress.indexOf(':') != stackAddress.lastIndexOf(':')
				&& stackAddress.charAt(0) != '['){
			stackAddress = '[' + stackAddress + ']';
		}
		stackName = PropertiesUtil.getProperty("javax.sip.STACK_NAME");
		if (stackName == null || "".equals(stackName)) {
			stackName = "GoPhone-1.1@" + Integer.toString(hashCode());
			// Add the stack name to the properties that will pass the stack
			PropertiesUtil.setProperty("javax.sip.STACK_NAME", stackName);
		}
		// ------------ application properties --------------
		publicAddress = PropertiesUtil
				.getProperty("edu.thu.thss.yxy.sip.PUBLIC_ADDRESS");
		if (publicAddress == null || "".equals(publicAddress))
			publicAddress = PropertiesUtil.getProperty("user.name") + "@"
					+ stackAddress;
		if (!publicAddress.trim().toLowerCase().startsWith("sip:"))
			publicAddress = "sip:" + publicAddress.trim();
		
		registrarAddress = PropertiesUtil
				.getProperty("edu.thu.thss.yxy.sip.REGISTRAR_ADDRESS");

		if (registrarAddress != null && registrarAddress.trim().length() != 0) {
			try {
				registrarPort = Integer.parseInt(PropertiesUtil
						.getProperty("edu.thu.thss.yxy.sip.REGISTRAR_PORT"));
			} catch (NumberFormatException ex) {
				registrarPort = 5060;
			}
			registrarTransport = PropertiesUtil.getProperty("edu.thu.thss.yxy.sip.REGISTRAR_TRANSPORT");
			if (registrarTransport == null || "".equals(registrarTransport))
				registrarTransport = DEFAULT_TRANSPORT;
			try {
				registrationsExpiration = Integer.parseInt(PropertiesUtil
								.getProperty("edu.thu.thss.yxy.sip.REGISTRATIONS_EXPIRATION"));
			} catch (NumberFormatException ex) {
				registrationsExpiration = 3600;
			}
		}
		
		PropertiesUtil.setProperty("javax.sip.RETRANSMISSION_FILTER", "true");
		String serverLog = PropertiesUtil
				.getProperty("gov.nist.javax.sip.SERVER_LOG");
		if (serverLog != null && !"".equals(serverLog)) {
			PropertiesUtil.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16");
		}
		
		sipStackPath = PropertiesUtil .getProperty("edu.thu.thss.yxy.sip.STACK_PATH");
		if (sipStackPath == null || "".equals(sipStackPath))
			sipStackPath = "gov.nist";
		
		String routerPath = PropertiesUtil.getProperty("javax.sip.ROUTER_PATH");
		if (routerPath == null || "".equals(routerPath)){
			PropertiesUtil.setProperty("javax.sip.ROUTER_PATH",
					"edu.thu.thss.yxy.sip.ProxyRouter");
		}
		
		transport = PropertiesUtil.getProperty("edu.thu.thss.yxy.sip.TRANSPORT");
		if (transport == null || "".equals(transport))
			transport = DEFAULT_TRANSPORT;
		
		String localPortStr = PropertiesUtil
				.getProperty("edu.thu.thss.yxy.sip.PREFERRED_LOCAL_PORT");
		try {
			localPort = Integer.parseInt(localPortStr);
		} catch (NumberFormatException exc) {
			localPort = 5060;
		}
		ourDisplayName = PropertiesUtil.getProperty("edu.thu.thss.yxy.sip.DISPLAY_NAME");
	}

	//============================= Utility Methods ==================================
    public FromHeader getFromHeader() throws OperationFailedException
    {
        if (fromHeader != null)
            return fromHeader;
        try {
            SipURI fromURI = (SipURI) addressFactory.createURI(publicAddress);
            fromURI.setTransportParam(listeningPoint.getTransport());
            fromURI.setPort(listeningPoint.getPort());
            Address fromAddress = addressFactory.createAddress(fromURI);
            if (ourDisplayName != null && ourDisplayName.trim().length() > 0) {
                fromAddress.setDisplayName(ourDisplayName);
            }
            fromHeader = headerFactory.createFromHeader(fromAddress,
                Integer.toString(hashCode()));
        }
        catch (ParseException ex) {
            throw new OperationFailedException(
                "A ParseException occurred while creating From Header!",OperationFailedException.GENERAL_ERROR, ex);
        }
        return fromHeader;
    }
    
	/**
     * Initializes and returns an ArrayList with a single ViaHeader
     * containing a localhost address usable with the specified
     * s<tt>destination</tt>. This ArrayList may be used when sending
     * requests to that destination.
     * <p>
     * @param intendedDestination The address of the destination that the
     * request using the via headers will be sent to.
     *
     * @return ViaHeader-s list to be used when sending requests.
     * @throws OperationFailedException code INTERNAL_ERROR if a ParseException
     * occurs while initializing the array list.
     *
     */
    public ArrayList<ViaHeader> getLocalViaHeaders(Address intendedDestination)
        throws OperationFailedException
    {
        return getLocalViaHeaders((SipURI)intendedDestination.getURI());
    }

    /**
     * Initializes and returns an ArrayList with a single ViaHeader
     * containing a localhost address usable with the specified
     * s<tt>destination</tt>. This ArrayList may be used when sending
     * requests to that destination.
     * <p>
     * @param intendedDestination The address of the destination that the
     * request using the via headers will be sent to.
     *
     * @return ViaHeader-s list to be used when sending requests.
     * @throws OperationFailedException code INTERNAL_ERROR if a ParseException
     * occurs while initializing the array list.
     *
     */
    public ArrayList<ViaHeader> getLocalViaHeaders(SipURI intendedDestination)
        throws OperationFailedException
    {
        if(viaHeaders != null){
        	return viaHeaders;
        } else{
        	viaHeaders = new ArrayList<ViaHeader>();
        }
        
        String transportParm = intendedDestination.getTransportParam();
        ListeningPoint srcListeningPoint = sipProvider.getListeningPoint(transportParm);
 
        try
        {
            int localPort = srcListeningPoint.getPort();
            String transport = srcListeningPoint.getTransport();
          

            ViaHeader viaHeader = headerFactory.createViaHeader(
            	getLocalHostAddress(),
                localPort,
                transport,
                null
                );
            viaHeaders.add(viaHeader);
            logger.debug("generated via headers:" + viaHeader);
            return viaHeaders;
        }
        catch (ParseException ex)
        {
            logger.error(
                "A ParseException occurred while creating Via Headers!", ex);
            throw new OperationFailedException(
                "A ParseException occurred while creating Via Headers!"
                ,OperationFailedException.INTERNAL_ERROR
                ,ex);
        }
        catch (InvalidArgumentException ex)
        {
            logger.error(
                "Unable to create a via header for port "
                + srcListeningPoint.getPort(),
                ex);
            throw new OperationFailedException(
                "Unable to create a via header for port "
                + srcListeningPoint.getPort()
                ,OperationFailedException.INTERNAL_ERROR
                ,ex);
        }
    }

    /**
     * Initializes and returns this provider's default maxForwardsHeader field
     * using the value specified by MAX_FORWARDS.
     *
     * @return an instance of a MaxForwardsHeader that can be used when
     * sending requests
     *
     * @throws OperationFailedException with code INTERNAL_ERROR if MAX_FORWARDS
     * has an invalid value.
     */
    public MaxForwardsHeader getMaxForwardsHeader() throws
        OperationFailedException
    {
        if (maxForwardsHeader == null)
        {
            try
            {
                maxForwardsHeader = headerFactory.createMaxForwardsHeader(
                    MAX_FORWARDS);
                logger.debug("generated max forwards: "
                             + maxForwardsHeader.toString());
            }
            catch (InvalidArgumentException ex)
            {
                throw new OperationFailedException(
                    "A problem occurred while creating MaxForwardsHeader"
                    , OperationFailedException.INTERNAL_ERROR
                    , ex);
            }
        }

        return maxForwardsHeader;
    }
    
	ContactHeader getContactHeader() throws OperationFailedException {
		return getContactHeader(false);
	}

	ContactHeader getContactHeader(boolean useLocalHostAddress)
			throws OperationFailedException {
		if (contactHeader != null)
			return contactHeader;
		try {
			SipURI contactURI;
			if (useLocalHostAddress)
				contactURI = (SipURI) addressFactory.createSipURI(null,
						getLocalHostAddress());
			else
				contactURI = (SipURI) addressFactory.createURI(publicAddress);
			contactURI.setTransportParam(listeningPoint.getTransport());
			contactURI.setPort(listeningPoint.getPort());
			Address contactAddress = addressFactory.createAddress(contactURI);
			if (ourDisplayName != null && ourDisplayName.trim().length() > 0) {
				contactAddress.setDisplayName(ourDisplayName);
			}
			contactHeader = headerFactory.createContactHeader(contactAddress);
		} catch (ParseException ex) {
			throw new OperationFailedException(
					"A ParseException occurred while creating From Header!",
					OperationFailedException.INTERNAL_ERROR, ex);
		}
		return contactHeader;
	}
    
	String getLocalHostAddress() {
		String hostAddress = PropertiesUtil.getProperty("javax.sip.IP_ADDRESS");

		if (hostAddress == null || "".equals(hostAddress))
			try {
				InetAddress localhost = InetAddress.getLocalHost();
				hostAddress = localhost.getHostAddress();
			} catch (UnknownHostException exc) {
				hostAddress = "127.0.0.1";
			}

		return hostAddress;
	}
	
	 /**
     * Returns the state of the registration of this protocol provider with the
     * corresponding registration service.
     * @return ProviderRegistrationState
     */
    public RegistrationState getRegistrationState()
    {
        if(this.registrarProcessor == null )
        {
            return RegistrationState.UNREGISTERED;
        }
        return registrarProcessor.getRegistrationState();
    }


	public void processRequest(RequestEvent requestEvent) {
		logger.debug("received request=\n" + requestEvent.getRequest());

		if (telePhonyProcessor != null) {
			if(!telePhonyProcessor.processRequest(requestEvent)){
				Request request = requestEvent.getRequest();

				// send a 489 / Bad Event response
				ServerTransaction serverTransaction = requestEvent.getServerTransaction();

				if (serverTransaction == null) {
					try {
						serverTransaction = sipProvider.getNewServerTransaction(request);
					} catch (TransactionAlreadyExistsException ex) {
						// let's not scare the user and only log a message
						logger.error("Failed to create a new server"
								+ "transaction for an incoming request\n"
								+ "(Next message contains the request)", ex);
						return;
					} catch (TransactionUnavailableException ex) {
						// let's not scare the user and only log a message
						logger.error("Failed to create a new server"
								+ "transaction for an incoming request\n"
								+ "(Next message contains the request)", ex);
						return;
					}
				}
				
				Response response = null;
				try {
					response = this.getMessageFactory().createResponse(
							Response.BAD_EVENT, request);
				} catch (ParseException e) {
					logger.error("failed to create the 489 response", e);
					return;
				}

				try {
					serverTransaction.sendResponse(response);
				} catch (SipException e) {
					logger.error("failed to send the response", e);
				} catch (InvalidArgumentException e) {
					// should not happen
					logger.error("invalid argument provided while trying"
							+ " to send the response", e);
				}
			}
		}
	}

	public void processResponse(ResponseEvent responseEvent) {
		logger.debug("received response=\n" + responseEvent.getResponse());
		ClientTransaction clientTransaction = responseEvent.getClientTransaction();
		if (clientTransaction == null) {
			logger.debug("ignoring a transactionless response");
			return;
		}

		Response response = responseEvent.getResponse();
		@SuppressWarnings("unused")
		String method = ((CSeqHeader) response.getHeader(CSeqHeader.NAME))
				.getMethod();

		// find the object that is supposed to take care of responses with the
		// corresponding method
		if (telePhonyProcessor != null) {
			telePhonyProcessor.processResponse(responseEvent);
		}
	}

	public void processTimeout(TimeoutEvent timeoutEvent) {
		 Transaction transaction;
	        if(timeoutEvent.isServerTransaction())
	            transaction = timeoutEvent.getServerTransaction();
	        else
	            transaction = timeoutEvent.getClientTransaction();

	        if (transaction == null)
	        {
	            logger.debug("ignoring a transactionless timeout event");
	            return;
	        }

	        Request request = transaction.getRequest();
	        logger.debug("received timeout for req=" + request);

	        //find the object that is supposed to take care of responses with the
	        //corresponding method
	        String method = request.getMethod();
	        if(method.equals(Request.REGISTER)){
	        	registrarProcessor.processTimeout(timeoutEvent);
	        } else if(method.equals(Request.INVITE)){
	        	telePhonyProcessor.processTimeout(timeoutEvent);
	        }else{
	        	logger.error("Received a TimeoutEvent while waiting on a message" 
	        			+ "\n(Check Details to see the message that caused it))");
	        }
	}


	public void processDialogTerminated(DialogTerminatedEvent evt) {}

	public void processIOException(IOExceptionEvent evt) {	}
	
	public void processTransactionTerminated(TransactionTerminatedEvent evt) {}

	public void addCallListener(CallListener callListener) {
		callListeners.add(callListener);
	}
	
	/**
     * Creates a RegistrationStateChange event corresponding to the specified
     * old and new states and notifies all currently registered listeners.
     *
     * @param oldState the state that the provider had before the change
     * occurred
     * @param newState the state that the provider is currently in.
     * @param reasonCode a value corresponding to one of the REASON_XXX fields
     * of the RegistrationStateChangeEvent class, indicating the reason for
     * this state transition.
     * @param reason a String further explaining the reason code or null if
     * no such explanation is necessary.
     */
	public void fireRegistrationStateChanged(RegistrationState oldState,
			RegistrationState newState, int reasonCode, String reason) {
		RegistrationStateChangeEvent event = new RegistrationStateChangeEvent(
				this, oldState, newState, reasonCode, reason);

		logger.debug("Received a RegistrationStateChangeEvent: " + event);

		if (newState.equals(RegistrationState.UNREGISTERED)) {
			logger.debug("We're unregistered and will notify those who wait");
			
			synchronized (this) {
				for(CallListener callListener : callListeners){
					callListener.unregistered(new RegistrationEvent(reason));
				}
			}
		}else if(newState.equals(RegistrationState.REGISTERED)){
			synchronized (this) {
				for(CallListener callListener : callListeners){
					callListener.registered(new RegistrationEvent(reason));
				}
			}
		}else if(newState.equals(RegistrationState.REGISTERING)){
			synchronized (this) {
				for(CallListener callListener : callListeners){
					callListener.registering(new RegistrationEvent(reason));
				}
			}
		}
	}
	
	/**
     * Creates and dispatches a <tt>CallEvent</tt> notifying registered
     * listeners that an event with id <tt>eventID</tt> has occurred on
     * <tt>sourceCall</tt>.
     *
     * @param eventID the ID of the event to dispatch
     * @param sourceCall the call on which the event has occurred.
     */
	public void fireCallEvent(int eventID, Call sourceCall) {
		CallEvent cEvent = new CallEvent(sourceCall, eventID);
		List<CallListener> listeners;

		synchronized (callListeners) {
			listeners = new ArrayList<CallListener>(callListeners);
		}

		logger.debug("Dispatching a CallEvent to " + listeners.size()
				+ " listeners. event is: " + cEvent);

		for (Iterator<CallListener> listenerIter = listeners.iterator(); listenerIter
				.hasNext();) {
			CallListener listener = listenerIter.next();

			switch (eventID) {
			case CallEvent.CALL_INITIATED:
				listener.outgoingCallCreated(cEvent);
				break;
			case CallEvent.CALL_RECEIVED:
				listener.incomingCallReceived(cEvent);
				break;
			case CallEvent.CALL_ENDED:
				listener.callEnded(cEvent);
				break;
			}
		}
	}

	public void sendServerInternalError(String callID) throws OperationFailedException{
		telePhonyProcessor.sayInteranlError(callID);
	}

	public void answerCall(String callID, String sdpData) throws OperationFailedException {
		telePhonyProcessor.answerCallPeer(callID, sdpData);
	}

	public Call createCall(String callee, String sdpData) throws OperationFailedException {	
		return telePhonyProcessor.createCall(callee, sdpData);
	}
	
	public void hangupCall(String callID) throws OperationFailedException {
		telePhonyProcessor.hangupCallPeer(callID);
	}
}
